Skip to content

refactor(composition): eliminate local_trigger_access; trigger-fire access is config + identity (§4.4) - #6374

Merged
ilblackdragon merged 5 commits into
mainfrom
refactor/eliminate-local-trigger-access
Jul 20, 2026
Merged

ilblackdragon merged 5 commits into
mainfrom
refactor/eliminate-local-trigger-access

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

What & why

Eliminates the last Bucket-1 Local* deployment-type leak from
docs/reborn/2026-07-17-architecture-simplification-dto-dyn-local.md §4.4:
the ironclaw_runner::local_trigger_access module (~1,464 LOC, libSQL +
Postgres backends). It was a shadow store duplicating state that already
exists — static owners are RebornBuildInput.owner_id; SSO users are the
StoredUser records the identity resolver persists on every login — and its
LocalTriggerAccessSource::LocalDev{Env,Sso,Run}Bootstrap enum was a
deployment-mode-as-type leak.

The prior refactor/deployment-config branch was already subsumed by #6279
(DeploymentConfig + the LocalDev* collapse) and is not used here.

Approach — trigger-fire access as config, no persisted store

  • TriggerFireAccessPolicy on RebornRuntimeInput: an OR-combined list of
    TriggerFireAccessGrant (StaticOwner{owner,agent,project} /
    TenantMembership{agent,project}), resolved at the serve/run edge.
  • Three checkers in ironclaw_reborn_composition::trigger_fire_access:
    StaticOwnerTriggerFireChecker (pure comparison, no I/O),
    IdentityMembershipTriggerFireChecker (membership from the identity
    RebornUserDirectory — the StoredUser SSO login already writes), and
    CompositeTriggerFireChecker (OR, with retryable-Unavailable precedence).
  • build_reborn_runtime builds the matching checker from the policy when
    the trigger poller is enabled. The SSO arm reads the runtime's own identity
    directory — resolving the pre/post-build lifecycle the standalone store
    dodged, and unifying the former local-libSQL / hosted-Postgres store
    fork
    into one identity-backed path.
  • serve/run set the policy; the per-login SSO trigger-access seed and the
    build_webui_auth_surface bootstrap parameter are removed.
  • Deletes the module, the composition re-exports + LocalTriggerAccessFire Checker + open_local_trigger_access_store +
    open_hosted_single_tenant_trigger_access_store, and the now-unused
    webui-user-store / filesystem-local-trigger-access Cargo features.

⚠️ Deliberate behavior change (tested)

SSO membership shifts from "was seeded on login" to "has an Active
StoredUser in this tenant."
This is faithful-or-stricter:

  • Admission (email allowlist) runs before resolve_or_create, and
    resolve_or_create rejects channel actors — so the admitted set is the same.
  • It newly honors suspension: the old seed-only store never revoked a
    suspended user's trigger-fire access; the identity-backed check denies them.
    Pinned by suspended_member_is_denied and the real-identity integration test.

Tests

  • 13 unit tests in trigger_fire_access.rs: static allow/deny/scope-mismatch;
    identity member/unknown/suspended/wrong-tenant/backend-error; composite
    OR; and real_identity_store_membership_backs_fire_access driving a real
    FilesystemRebornIdentityStore through resolve_or_create → membership check
    → suspend → deny (crate-tier because the checker is pub(crate); an external
    tests/ file can't construct it).
  • serve/run/webui_auth/user_directory tests rewritten to assert the resolved
    TriggerFireAccessPolicy (union preserved when poller + SSO are both on).

Enforcement / scope

  • reborn_deployment_mode_typename_ratchet.rs: LocalTriggerAccess* /
    Reborn*LocalTriggerAccess* entries removed from the allowlist (the module
    is gone). Composition pub-use snapshot regenerated.
  • Net −1,592 LOC (897 insertions, 2,489 deletions). serve.rs left shorter.
  • Out of scope (noted follow-up): LocalInvocationServicesResolver is a
    Bucket-2 mechanical de-prefix, not a deployment type — kept in the allowlist.

Verification

cargo fmt; cargo test -p ironclaw_reborn_composition --features webui-v2-beta; cargo test -p ironclaw_runner --features filesystem-goal-store; cargo test -p ironclaw_architecture; clippy on
composition/cli/runner under both webui-v2-beta and default features;
cargo check --workspace; scripts/pre-commit-safety.sh (composition
mass/dispatch within budget). All green.

🤖 Generated with Claude Code

…gger-fire access is config + identity (§4.4)

Last Bucket-1 `Local*` deployment-type leak from
docs/reborn/2026-07-17-architecture-simplification-dto-dyn-local.md §4.4.
The `ironclaw_runner::local_trigger_access` module (~1,464 LOC, libSQL +
Postgres backends) was a shadow store duplicating state that already exists:
static owners are `RebornBuildInput.owner_id`, and SSO users are the
`StoredUser` records the identity resolver persists on login. Its
`LocalTriggerAccessSource::LocalDev{Env,Sso,Run}Bootstrap` enum was a
deployment-mode-as-type leak.

Replace it with a config value and no persisted store:

- `TriggerFireAccessPolicy` on `RebornRuntimeInput` — an OR-combined list of
  `TriggerFireAccessGrant`s (`StaticOwner` / `TenantMembership`), resolved at
  the serve/run edge.
- Three checkers in `ironclaw_reborn_composition::trigger_fire_access`:
  `StaticOwnerTriggerFireChecker` (pure comparison), `IdentityMembership
  TriggerFireChecker` (membership from the identity `RebornUserDirectory`),
  and `CompositeTriggerFireChecker` (OR, retryable-unavailable precedence).
- `build_reborn_runtime` builds the matching checker from the policy when the
  trigger poller is enabled; the SSO arm reads the runtime's own identity
  directory (fixes the pre/post-build lifecycle the standalone store dodged)
  and unifies the former local-libSQL / hosted-Postgres store fork.
- `serve`/`run` set the policy; the per-login SSO trigger-access seed and the
  `build_webui_auth_surface` bootstrap parameter are removed.
- Delete the module, the composition re-exports + `LocalTriggerAccessFire
  Checker` + `open_local_trigger_access_store` + `open_hosted_single_tenant_
  trigger_access_store`, and the now-unused `webui-user-store` /
  `filesystem-local-trigger-access` Cargo features.

Deliberate, tested behavior change: SSO membership shifts from "seeded on
login" to "Active StoredUser in this tenant" — faithful (admission runs before
user creation; channel actors can't mint) and stricter (suspension now revokes
trigger-fire access, which the seed-only store never did).

Enforcement + coverage: `FROZEN_OTHER_MODE_TYPES` trimmed of the
`LocalTriggerAccess*` / `Reborn*LocalTriggerAccess*` entries; composition
pub-use snapshot regenerated; 13 checker/integration unit tests (incl. a real
`FilesystemRebornIdentityStore`-backed membership test asserting suspension
revokes); serve/run/webui_auth/user_directory tests rewritten to assert the
policy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ironloopai

ironloopai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: be36610e6e10499f4a4313e39d89396b4e2f28a5
Result: One or more review results were superseded by a newer PR head.
Next: Run @ironloopai review on the latest PR head.
Updated: 2026-07-20T23:18:03.480Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Superseded N/A N/A 2026-07-20T23:18:03.464Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Superseded by a newer PR head. New head: be36610. Previous verdict: Changes requested.
Recent activity
Time Reviewer State Detail
2026-07-20T22:01:27.435Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (9255396).
2026-07-20T22:56:39.260Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head 6a849b7.
2026-07-20T22:56:39.260Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-20T22:56:39.889Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-20T22:56:43.236Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (merge_ref) at 6d4e194.
2026-07-20T23:03:44.902Z ironloop/common-reviewer (reviewer) Result captured Changes requested; 2 blocking findings.
2026-07-20T23:03:44.902Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
2026-07-20T23:18:03.464Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (be36610).
Available commands
  • @ironloopai help
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent>
Run metadata

Admission: webhook accepted the request and IronLoop persisted reviewer state before this projection.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6374 July 20, 2026 21:55 Destroyed
@github-actions github-actions Bot added scope: docs Documentation size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added configurable trigger-fire access policies with owner-based and tenant-membership grants, including OR-combined authorization.
  • Refactor
    • WebUI authentication and runtime now derive effective trigger-fire authorization from policy configuration (static-owner and identity-membership) instead of seeding legacy local trigger-access storage.
    • Removed local trigger-access store/checker bootstrapping paths and related WebUI surface wiring.
  • Tests
    • Updated to validate policy combination/decisions and revocation behavior; removed legacy store/checker tests.
  • Documentation
    • Refreshed invariants examples to reflect the new authorization approach.

Walkthrough

The change replaces local trigger-access persistence and bootstrap wiring with configured trigger-fire policies, runtime-composed authorization checkers, and identity-directory membership checks. Feature flags, exports, tests, ratchets, and documentation are updated.

Changes

Trigger-fire access migration

Layer / File(s) Summary
Policy contracts and checkers
crates/ironclaw_reborn_composition/src/runtime_input.rs, crates/ironclaw_reborn_composition/src/trigger_fire_access.rs, crates/ironclaw_reborn_composition/src/lib.rs
Adds static-owner and tenant-membership grants, policy builders, composed checkers, exact-scope matching, and authorization tests.
Runtime policy wiring
crates/ironclaw_reborn_composition/src/runtime.rs, crates/ironclaw_reborn_cli/src/commands/serve.rs, crates/ironclaw_reborn_cli/src/runtime/mod.rs
Builds effective trigger-poller authorization from configured grants or explicit checker overrides, and derives CLI policies from poller and SSO settings.
WebUI identity integration
crates/ironclaw_reborn_cli/src/commands/user_directory.rs, crates/ironclaw_reborn_cli/src/commands/webui_auth.rs
Removes local trigger-access seeding from user resolution and authentication-surface construction.
Local store removal and repository cleanup
crates/ironclaw_runner/*, crates/ironclaw_reborn_composition/Cargo.toml, crates/ironclaw_reborn_composition/src/input.rs, docs/plans/*, crates/ironclaw_architecture/tests/*
Removes local trigger-access feature and API wiring and updates persistence tests, public snapshots, type ratchets, CI flags, and related documentation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant RebornRuntimeInput
  participant build_reborn_runtime
  participant TriggerFireChecker
  CLI->>RebornRuntimeInput: set TriggerFireAccessPolicy
  RebornRuntimeInput->>build_reborn_runtime: provide policy and optional override
  build_reborn_runtime->>TriggerFireChecker: compose grant-based checker
  TriggerFireChecker-->>build_reborn_runtime: effective authorization checker
Loading

Possibly related issues

Suggested reviewers: serrrfirat, henrypark133

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits and accurately summarizes the main refactor.
Description check ✅ Passed The description covers the change, rationale, tests, and verification, though several template sections like Change Type and Linked Issue are omitted.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ironloopai ironloopai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⏭️ IronLoop Review Declined: reviewer

Review at a glance

Disposition Head
⏭️ Review declined 75703603734e

Head: 75703603734edda850b69e6b3388b2377882d9f4
Reason: The actual comparison changes 641 files with 55,765 insertions and 29,671 deletions (including 18 binary files), spanning many unrelated crates, CI, documentation, and frontend areas. This cannot be reviewed reliably within the configured scope; it also materially exceeds the PR description's stated ~1.6k LOC change.
Next: Split the unrelated changes into separate PRs, or provide the intended intermediate base SHA containing the prerequisite work so the trigger-fire-access delta can be reviewed independently.

Run details

Status: Current
Trustworthy review produced: no

Summary

Skipped: the supplied base-to-head comparison is an oversized, cross-cutting mega-diff rather than a reviewable focused refactor.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request simplifies the architecture by removing the persisted parallel local_trigger_access store and replacing it with a configuration-driven TriggerFireAccessPolicy on RebornRuntimeInput. Fire-time trigger authorization is now performed via either a pure comparison against a config-supplied static owner or a membership lookup against the canonical identity directory. The review feedback recommends avoiding the recently stabilized is_none_or method to maintain MSRV compatibility, and optimizing CompositeTriggerFireChecker to prevent cloning the request on the final iteration of the loop.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +127 to +133
let allowed = user.is_some_and(|user| {
user.status == ironclaw_reborn_identity::RebornUserStatus::Active
&& user
.tenant_id
.as_ref()
.is_none_or(|tenant| tenant == &self.tenant_id)
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using nightly or recently stabilized features like is_none_or if they are not supported by the project's MSRV. Consider using an explicit match expression instead to avoid compatibility issues and potential clippy warnings.

Suggested change
let allowed = user.is_some_and(|user| {
user.status == ironclaw_reborn_identity::RebornUserStatus::Active
&& user
.tenant_id
.as_ref()
.is_none_or(|tenant| tenant == &self.tenant_id)
});
let allowed = user.is_some_and(|user| {
user.status == ironclaw_reborn_identity::RebornUserStatus::Active
&& match user.tenant_id.as_ref() {
Some(tenant) => tenant == &self.tenant_id,
None => true,
}
});
References
  1. Avoid using nightly Rust features in production-grade crates. If a nightly feature like is_none_or is suggested, consider alternative stable Rust constructs like explicit match expressions to avoid compatibility issues and potential clippy warnings.

Comment on lines +157 to +175
async fn check_trigger_fire_access(
&self,
request: TriggerFireAccessCheck,
) -> Result<TriggerFireAccessDecision, TriggerFireAccessError> {
let mut unavailable: Option<TriggerFireAccessError> = None;
for checker in &self.checkers {
match checker.check_trigger_fire_access(request.clone()).await {
Ok(TriggerFireAccessDecision::Allowed) => {
return Ok(TriggerFireAccessDecision::Allowed);
}
Ok(TriggerFireAccessDecision::Denied { .. }) => {}
Err(error) => unavailable = Some(error),
}
}
match unavailable {
Some(error) => Err(error),
None => Ok(denied()),
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In CompositeTriggerFireChecker::check_trigger_fire_access, request is cloned on every iteration of the loop. However, on the last iteration, we can consume request directly without cloning it, avoiding unnecessary heap allocations for the various IDs contained in TriggerFireAccessCheck. We can achieve this cleanly by using split_last() on the checkers slice.

    async fn check_trigger_fire_access(
        &self,
        request: TriggerFireAccessCheck,
    ) -> Result<TriggerFireAccessDecision, TriggerFireAccessError> {
        let mut unavailable: Option<TriggerFireAccessError> = None;
        if let Some((last, rest)) = self.checkers.split_last() {
            for checker in rest {
                match checker.check_trigger_fire_access(request.clone()).await {
                    Ok(TriggerFireAccessDecision::Allowed) => {
                        return Ok(TriggerFireAccessDecision::Allowed);
                    }
                    Ok(TriggerFireAccessDecision::Denied { .. }) => {}
                    Err(error) => unavailable = Some(error),
                }
            }
            match last.check_trigger_fire_access(request).await {
                Ok(TriggerFireAccessDecision::Allowed) => {
                    return Ok(TriggerFireAccessDecision::Allowed);
                }
                Ok(TriggerFireAccessDecision::Denied { .. }) => {}
                Err(error) => unavailable = Some(error),
            }
        }
        match unavailable {
            Some(error) => Err(error),
            None => Ok(denied()),
        }
    }

…ackage flags

The local_trigger_access elimination removed the webui-user-store feature;
the runner CI package-flags line still requested it, which would fail feature
resolution. libSQL coverage stays via libsql-secrets + libsql-restart-tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6374 July 20, 2026 22:01 Destroyed
@railway-app

railway-app Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-6374 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw 🕒 Building (View Logs) Web Jul 20, 2026 at 11:18 pm

Main deleted `webui-v2-beta` (and 13 other compile-time features, #6296:
"the beta host mounts are unconditional"). This branch was written against a
pre-#6296 main and gated all its new code on `#[cfg(feature = "webui-v2-beta")]`.
Reconciled by un-gating:

- trigger_fire_access.rs: IdentityMembershipTriggerFireChecker (+ its impls and
  the identity test module) and the TenantId import are now unconditional;
  `ironclaw_reborn_identity` is a plain composition dep on main.
- runtime.rs: the `TenantMembership` grant arm is unconditional; deleted the
  `#[cfg(not(feature = "webui-v2-beta"))]` dead-alternate arm (per
  .claude/rules/cargo-features.md).
- runtime/mod.rs (cli): un-gated the policy imports and run-path wiring.
- Cargo.toml: dropped the `webui-user-store` / `filesystem-local-trigger-access`
  runner feature forwards and the base `webui-user-store` runner dep feature;
  package-feature-flags.sh runner line reconciled to
  `libsql-secrets,libsql-restart-tests` (both stale sides corrected).
- Conflicts in lib.rs / input.rs / facade_factory.rs resolved by keeping this
  branch's deletion of the local_trigger_access re-exports, checker, and hosted
  store; composition pub-use snapshot regenerated.

Verified on merged tree: clippy -D warnings (composition, ironclaw, runner,
default features) clean; ironclaw_architecture green (incl. pub-use snapshot);
composition/runner/cli test suites pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6374 July 20, 2026 22:45 Destroyed
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6374 July 20, 2026 22:51 Destroyed
@ilblackdragon

Copy link
Copy Markdown
Member Author

@ironloopai review

@ironloopai ironloopai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ IronLoop Review: reviewer

Review at a glance

Verdict Blocking Notes Inline Head
❌ Changes requested 2 0 2 6a849b76e7e4

Head: 6a849b76e7e4990285ea729411034afa0521ccd1
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.

Run details

Status: Current
Needs human: no
Needs validation: no

Summary

The new policy checkers lose tenant isolation and deny legacy SSO users whose identity migration has no StoredUser record, permanently failing their scheduled fires.

Findings

Blocking: 2 / Notes: 0

Blocking findings

1. ❌ [HIGH] Constrain trigger-fire grants to the poller tenant

Location: crates/ironclaw_reborn_composition/src/trigger_fire_access.rs:65-66
Neither new policy checker verifies request.tenant_id against the runtime tenant: this static grant only compares owner and scope, while the membership checker compares the stored user to its own tenant. list_due_triggers scans the shared repository across tenants, so a poller can authorize and execute a foreign-tenant trigger when owner and agent/project coincide. The deleted store checked the request tenant exactly. Bind the policy/authorizer to the runtime tenant and add a foreign-tenant same-owner/scope denial test.

2. ❌ [HIGH] Preserve trigger access for migrated legacy SSO users

Location: crates/ironclaw_reborn_composition/src/trigger_fire_access.rs:112
The legacy user_identities fold only writes external-identity/index records, not a StoredUser; the resolver explicitly allows an identity pointing to a missing user record. Those users can still log in, but this lookup returns None and the new membership policy denies their existing triggers. Authorization denial becomes permanent trigger materialization failure, including terminal failure for one-shot triggers. Hydrate/migrate an active StoredUser for legacy identities (or retain equivalent access) and cover a migrated identity firing an existing trigger.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloopai review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloopai review when the fix may affect multiple areas.

&self,
request: TriggerFireAccessCheck,
) -> Result<TriggerFireAccessDecision, TriggerFireAccessError> {
let allowed = request.creator_user_id == self.owner

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This policy must also reject a request.tenant_id outside the runtime tenant. The due-trigger repository is global, so owner/scope-only matching can authorize a foreign tenant's record; the previous store checked tenant as part of the exact access tuple.

if !scope_matches(&request, &self.agent, &self.project) {
return Ok(denied());
}
let user = self

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy user_identities migration does not create StoredUser rows, while legacy identity resolution intentionally permits a missing user record. Those users will always get None here and their existing scheduled triggers now fail permanently. Preserve/hydrate their membership before relying on this directory lookup.

@github-actions

github-actions Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 86.4% (321581 / 372217 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 372217 lines now vs 320188 at floor capture (+52029 lines, +16.25%) — material change (>5%)

⚠️ 2 Reborn crate(s) have 0 int-tier coverage (target: 0) — ironclaw_prompt_envelope, ironclaw_scripts

Reborn integration-tier coverage

Line coverage (Reborn crates): 86.4% — 321581 / 372217 lines

Per-crate breakdown (65 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_runtime_policy 33.84% 89 / 263
ironclaw_event_projections 43.31% 673 / 1554
ironclaw_observability 61.54% 16 / 26
ironclaw_authorization 62.46% 604 / 967
ironclaw_dispatcher 62.88% 83 / 132
ironclaw_mcp 64.89% 595 / 917
ironclaw_filesystem 68.34% 4121 / 6030
ironclaw_channel_host 68.65% 219 / 319
ironclaw_memory 69.2% 773 / 1117
ironclaw_reborn_migration 70.42% 2362 / 3354
ironclaw_trust 72.88% 661 / 907
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_extractors 74.72% 538 / 720
ironclaw_capabilities 75.76% 2103 / 2776
ironclaw_projects 76.48% 400 / 523
ironclaw_reborn_cli 76.54% 9890 / 12922
ironclaw_triggers 77.33% 2531 / 3273
ironclaw_llm 78.36% 20306 / 25915
ironclaw_product_context 78.57% 11 / 14
ironclaw_telegram_extension 80.18% 4842 / 6039
ironclaw_wasm_product_adapters 80.36% 1448 / 1802
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_first_party_extensions 81.06% 5965 / 7359
ironclaw_memory_native 81.17% 3195 / 3936
ironclaw_events 81.95% 1594 / 1945
ironclaw_network 82.98% 673 / 811
ironclaw_reborn_event_store 83.03% 1169 / 1408
ironclaw_reborn_identity 83.59% 433 / 518
ironclaw_processes 83.76% 939 / 1121
ironclaw_secrets 83.79% 2548 / 3041
ironclaw_wasm 84.44% 1069 / 1266
ironclaw_reborn_config 84.66% 2152 / 2542
ironclaw_product_workflow 84.75% 11088 / 13083
ironclaw_auth 84.97% 3279 / 3859
ironclaw_run_state 85.61% 458 / 535
ironclaw_channel_delivery 85.79% 1383 / 1612
ironclaw_common 86.13% 1714 / 1990
ironclaw_threads 87.22% 4838 / 5547
ironclaw_slack_v2_adapter 87.3% 1491 / 1708
ironclaw_skills 87.6% 4471 / 5104
ironclaw_turns 87.88% 14530 / 16533
ironclaw_product_adapter_registry 88.06% 531 / 603
ironclaw_product_adapters 88.1% 3384 / 3841
ironclaw_reborn_traces 88.2% 11946 / 13544
ironclaw_host_runtime 88.69% 18153 / 20467
ironclaw_reborn_openai_compat 88.79% 3778 / 4255
ironclaw_host_api 88.83% 4635 / 5218
ironclaw_webui 89.33% 7700 / 8620
ironclaw_extensions 89.33% 2955 / 3308
ironclaw_telegram_v2_adapter 89.65% 2712 / 3025
ironclaw_reborn_composition 89.8% 75736 / 84341
ironclaw_approvals 90.18% 1598 / 1772
ironclaw_conversations 90.39% 3123 / 3455
ironclaw_event_streams 90.82% 1009 / 1111
ironclaw_hooks 90.88% 10075 / 11086
ironclaw_runner 91.08% 16946 / 18606
ironclaw_resources 91.67% 4477 / 4884
ironclaw_loop_host 92.24% 15992 / 17338
ironclaw_attachments 93.06% 630 / 677
ironclaw_agent_loop 94.95% 9424 / 9925
ironclaw_safety 95.09% 3682 / 3872
ironclaw_outbound 95.52% 3451 / 3613
ironclaw_first_party_extension_ports 95.62% 3672 / 3840

This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors.

Exemptions (3 entry/entries excluded from the accounting above)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657

…nt composite clone

Addresses PR #6374 review:
- StaticOwnerTriggerFireChecker now rejects a foreign request.tenant_id. The
  due-trigger repository is global, so owner+scope-only matching could authorize
  another tenant's trigger whose creator id equals this owner — the former store
  keyed every row on tenant. Tenant is injected from the runtime scope at build.
  Regression test: static_owner_denies_foreign_tenant.
- CompositeTriggerFireChecker passes the request to its last checker by move
  (split_last), dropping one clone per fire on the common StaticOwner+SsoMembership
  pair.
- Kept is_none_or (stable 1.82; workspace is edition 2024 / Rust >=1.85) with a
  comment — clippy enforces it over map_or.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6374 July 20, 2026 23:18 Destroyed
@ilblackdragon

Copy link
Copy Markdown
Member Author

Thanks — addressed the automated review in be36610e6:

  1. Static-owner foreign-tenant authorization (trigger_fire_access.rs:65) — real regression, fixed. StaticOwnerTriggerFireChecker now binds to a tenant_id (injected from the runtime scope at build) and rejects any request.tenant_id that doesn't match, restoring the tenant keying the former store had. Regression test static_owner_denies_foreign_tenant. The IdentityMembership checker already gated on user.tenant_id.

  2. Composite clones request every iteration (trigger_fire_access.rs:170) — fixed. Uses split_last so the final checker takes request by move; drops one clone per fire on the common StaticOwner + SsoMembership pair.

  3. is_none_or MSRV (trigger_fire_access.rs:128) — kept, with a comment. This workspace is edition 2024 (Rust ≥ 1.85), and is_none_or is stable since 1.82, so it's within MSRV; clippy actively enforces it over map_or(true, …) (a map_or rewrite fails -D warnings).

  4. Legacy user_identities users lack StoredUser (trigger_fire_access.rs:112) — acknowledged; deferring to the identity crate. A user migrated by the pre-Add canonical Reborn identity resolver for OAuth and external actor binding #4381 libSQL fold has no StoredUser row (the fold seeds identity/index records only), and a returning login doesn't create one, so identity-backed membership would deny their fires. That gap is the identity store's tracked issue (ironclaw_reborn_identity: adopt_migrated_identity never writes StoredUser and reverses the index/identity write order #5616: migrated identities don't get StoredUser rows) — the correct fix is a backfill there, not a special-case in the fire-time checker, which should read the canonical membership store. Filed/linked rather than worked around here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ironclaw_reborn_composition/src/trigger_fire_access.rs (1)

125-131: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Log the bound source error before mapping.

The map_err closure drops the original RebornIdentityError cause chain by converting it to a string. As per coding guidelines, you must preserve the source error or log the bound source before mapping when a constructor (like Unavailable) only accepts a string representation.

♻️ Proposed fix
         let user = self
             .directory
             .get_user(&request.creator_user_id)
             .await
-            .map_err(|error| TriggerFireAccessError::Unavailable {
-                reason: error.to_string(),
+            .map_err(|error| {
+                tracing::warn!(%error, "identity directory lookup failed during trigger fire access check");
+                TriggerFireAccessError::Unavailable {
+                    reason: error.to_string(),
+                }
             })?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_reborn_composition/src/trigger_fire_access.rs` around lines
125 - 131, Update the error handling around the directory lookup in the
trigger-fire access flow to log the original RebornIdentityError before
converting it into TriggerFireAccessError::Unavailable. Bind the source error in
the map_err closure, emit it through the existing logging mechanism, and retain
the current reason string and error mapping behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/ironclaw_reborn_composition/src/trigger_fire_access.rs`:
- Around line 125-131: Update the error handling around the directory lookup in
the trigger-fire access flow to log the original RebornIdentityError before
converting it into TriggerFireAccessError::Unavailable. Bind the source error in
the map_err closure, emit it through the existing logging mechanism, and retain
the current reason string and error mapping behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a8bfa5ce-2874-42c5-9186-576a33ab528e

📥 Commits

Reviewing files that changed from the base of the PR and between 6a849b7 and be36610.

📒 Files selected for processing (2)
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/trigger_fire_access.rs

@ilblackdragon
ilblackdragon merged commit 5b307e2 into main Jul 20, 2026
68 checks passed
@ilblackdragon
ilblackdragon deleted the refactor/eliminate-local-trigger-access branch July 20, 2026 23:45
ilblackdragon added a commit that referenced this pull request Jul 21, 2026
…m-goal-store (#6378)

Continues the runner feature-flag cleanup after #6374 removed
`local_trigger_access` (and with it `webui-user-store` /
`filesystem-local-trigger-access`). Removes the two remaining flags that
no shipped build shape turns off, leaving `libsql-restart-tests` as the
runner's sole flag — a sanctioned CI test-lane selector with zero `src/`
`#[cfg]`.

libsql-secrets:
- Gated `src/secrets.rs`, a libSQL `FilesystemSecretStore` assembly no
  shipped build enabled (only the CI compile self-test). The production
  assembly already lives in `ironclaw_reborn_composition::factory`
  (`build_secret_store` / `open_local_dev_secret_store`).
- Removes the module + `tests/secrets.rs`, the feature, and the now-orphaned
  optional deps `ironclaw_secrets` and `secrecy`.

filesystem-goal-store:
- Gated `FilesystemSubagentGoalStore` + the `await_edge` submodules,
  isolating only `ironclaw_filesystem` (a cheap path dep). It was forwarded
  by composition's *both* `libsql` and `postgres` features and by
  product_workflow — on in every build, i.e. the product, not a build shape.
- Makes `ironclaw_filesystem` an unconditional dep and de-gates the code;
  drops the forwards in composition (`libsql`/`postgres`) and the
  product_workflow dev-dep feature.

De-gating also resolves the pre-existing dead-code warnings in the runner's
zero-feature build (the `await_edge`/`untrusted_text` helpers are now always
compiled and reachable).

Behavior unchanged: both modules/paths were on in every shipped build or
dead in all of them. Verified: runner tests (default), runner clippy
(default + libsql-restart-tests), workspace feature matrix (default +
all-features), and `cargo test -p ironclaw_architecture`.

Supersedes #6377.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BenKurrek added a commit that referenced this pull request Jul 21, 2026
…y cutover, #6386 authorize() consolidation, #6408 outbound caller-scoping)

Eighth fold. Dispositions follow the standing philosophy (never merge-as-is,
never drop a feature); ledger entry lands in the PR body.

- Tier B adopted wholesale: src/ + gateway/tui crates deleted, root package is
  the test-only ironclaw_reborn_integration_tests host, root Dockerfile is the
  Reborn image (de-migrated per owner decision D1 — this tree deletes
  ironclaw_reborn_migration; the D1 absence pin moves to the root Dockerfile),
  legacy test_rig/support files gone with their [[test]] entries.
- #6386 authorize() consolidation: production side taken verbatim; the
  local-manifest trust test main relocated to ironclaw_capabilities::trust is
  re-expressed in this tree's manifest dialect (host_api sections + contracts
  registry arg).
- #6408 outbound caller-scoping made structural on the generic lane: the
  OutboundDeliveryTargetOwner vocabulary + owner field are defined locally in
  composition (ironclaw_channel_host stays deleted), both registry paths keep
  main's entry_owned_by_caller filtering, and the generic channel provider
  stamps owners from the resolved resource (subject route / DM record user),
  never the caller.
- #6374 trigger-fire access as config: main's TriggerFireAccessPolicy wiring
  and serve tests adopted; the LocalTriggerAccess* store lane stays deleted.
- #6395 SSO/admin identity resolver: production-substrate branch adopted;
  the legacy libSQL identity fold stays out (greenfield blank-slate).
- #6387 factory/facade test extraction: main's module topology adopted; this
  branch's test-module content three-way-merged into factory/tests.rs and
  webui/facade/tests.rs.
- CI: package allowlist keeps this tree's crate set + main's root-package
  exclusion; bucket lanes for deleted crates removed (self-test repinned).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-6374 — be36610e Deployed Jul 20, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant